package com.seed.application.service.save;

import cn.hutool.core.util.StrUtil;
import com.mybatisflex.core.row.Db;
import com.mybatisflex.core.row.Row;
import com.mybatisflex.core.row.RowKey;
import com.seed.application.validator.SaveValidator;
import com.seed.core.action.SaveAction;
import com.seed.core.builder.declare.DeclaredBuilder;
import com.seed.core.column.Column;
import com.seed.core.enumeration.PropertyType;
import com.seed.core.listener.OnSaveListener;
import com.seed.core.pojo.Const;
import com.seed.core.pojo.seed.*;
import com.seed.core.query.pojo.Declared;
import com.seed.util.DeclareKit;
import com.seed.util.RowKit;
import com.seed.util.SeedKit;
import com.seed.util.ThrowKit;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

public class SaveService {
    private final Seed seed;
    private final SaveAction action;

    public SaveService(Seed seed, SaveAction action) {
        this.seed = seed;
        this.action = action;
    }

    public boolean exec(Row data) {
        Map<String, Declared> declareds = new DeclaredBuilder(seed, getDeclares()).build();
        save(seed, declareds, data);
        return true;
    }

    public boolean execBatch(List<Row> dataList) {
        Map<String, Declared> declareds = new DeclaredBuilder(seed, getDeclares()).build();
        saveBatch(seed, declareds, dataList);
        return true;
    }

    private List<String> getDeclares() {
        // 带路径的配置
        List<String> declares = action.getDeclares();
        if(declares != null) return declares;

        declares = seed.getDeclares();
        if(declares != null) return declares;

        return SeedKit.getDefaultDeclares(seed);
    }

    public static void save(Seed seed, Map<String, Declared> declareds, Row self) {
        Row mainRow = saveMainData(seed, declareds, self);
        saveRelationData(mainRow, declareds, self);
    }

    public static void saveBatch(Seed seed, Map<String, Declared> declareds, List<Row> dataList) {
        if(dataList.isEmpty()) return;

        if(DeclareKit.isPlain(declareds)) {
            List<Row> rows = new ArrayList<>();
            for(Row row: dataList) {
                List<? extends OnSaveListener> listeners = seed.getOnSave();
                if(listeners != null) {
                    for(OnSaveListener listener: listeners) listener.onSave(row);
                }
                rows.add(row);
            }

            SaveValidator.validate(seed, rows);
            Db.insertBatch(seed.getTableName(), rows);
        } else {
            for (Row row: dataList) save(seed, declareds, row);
        }
    }

    /**
     * 保存主数据
     * @param seed 元数据
     * @param declareds 对应的申明
     * @param self 等提取的数据
     */
    private static Row saveMainData(Seed seed, Map<String, Declared> declareds, Row self) {
        Row mainRow = Row.ofKey(RowKey.FLEX_ID);
        for(Map.Entry<String, Declared> entry: declareds.entrySet()) {
            String crossOrColumn = entry.getKey();
            Declared value = entry.getValue();
            PropertyType propertyType = value.getType();
            switch (propertyType) {
                case none, asterisk -> { // 所有列
                    Map<String, Column> columns = seed.getColumns();
                    for (Column column: columns.values()) {
                        String columName = column.getName();
                        if(Const.ID.equals(columName)) continue;

                        mainRow.set(columName, self.get(StrUtil.toCamelCase(columName)));
                    }
                }
                case column -> { // 单个列
                    Column column = (Column) value.getProperty();
                    String columName = column.getName();
                    if(Const.ID.equals(columName)) continue;

                    mainRow.set(columName, self.get(StrUtil.toCamelCase(columName)));
                }
                case belongsTo -> { // 外键列
                    BelongsTos belongsTo = (BelongsTos) value.getProperty();
                    String fieldName = StrUtil.toCamelCase(crossOrColumn);
                    Row target = (Row) self.get(fieldName);
                    String inferSelfColumn = belongsTo.inferSelfColumn();
                    String targetField = StrUtil.toCamelCase(belongsTo.inferTargetColumn());
                    mainRow.set(inferSelfColumn, target == null ? null : target.get(targetField));
                }
            }
        }

        List<? extends OnSaveListener> listeners = seed.getOnSave();
        if(listeners != null) {
            for(OnSaveListener listener: listeners) listener.onSave(mainRow);
        }

        SaveValidator.validate(seed, mainRow);
        int result = Db.insert(seed.getTableName(), mainRow);
        if(result == 0) ThrowKit.exception("插入数据失败");

        return mainRow;
    }

    /**
     * 保存关系数据
     * @param mainRow 主数据
     * @param declareds 待提取数据对应的申明
     * @param self 待提取数据
     */
    private static void saveRelationData(Row mainRow, Map<String, Declared> declareds, Row self) {
        for(Map.Entry<String, Declared> entry: declareds.entrySet()) {
            String crossOrColumn = entry.getKey();
            String fieldName = StrUtil.toCamelCase(crossOrColumn);
            Declared value = entry.getValue();
            PropertyType propertyType = value.getType();
            switch (propertyType) {
                case hasOne -> {
                    HasOnes hasOne = (HasOnes) value.getProperty();
                    Seed targetSeed = hasOne.targetSeed();
                    Row target = (Row) self.get(fieldName);
                    if(target == null) continue;

                    String inferSelfColumn = hasOne.inferSelfColumn();
                    String targetField = StrUtil.toCamelCase(hasOne.inferTargetColumn());
                    target.set(targetField, mainRow.get(inferSelfColumn));
                    save(targetSeed, value, target);
                }
                case hasMany -> {
                    Object targetObj = self.get(fieldName);
                    if (targetObj == null) return;

                    HasManys hasMany = (HasManys) value.getProperty();
                    Seed targetSeed = hasMany.targetSeed();

                    Collection<Row> target;
                    if(targetObj.getClass().isArray()) {
                        target = List.of((Row[]) targetObj);
                    } else if(targetObj instanceof Collection<?>) {
                        target = (Collection<Row>) targetObj;
                    } else {
                        throw new RuntimeException(String.format("不支持的类型【%s】", targetObj.getClass()));
                    }

                    List<Row> adding = new ArrayList<>();
                    for(Map<String, Object> map: target) {
                        Row t = RowKit.toRow(map);
                        String inferSelfColumn = hasMany.inferSelfColumn();
                        String targetField = StrUtil.toCamelCase(hasMany.inferTargetColumn());
                        t.set(targetField, mainRow.get(inferSelfColumn));
                        adding.add(t);
                    }
                    saveBatch(targetSeed, value, adding);
                }
                case habtm -> {
                    Object targetObj = self.get(fieldName);
                    if(targetObj == null) continue;

                    Habtms habtm = (Habtms) value.getProperty();

                    Collection<Row> target;
                    if(targetObj.getClass().isArray()) {
                        target = List.of((Row[]) targetObj);
                    } else if(targetObj instanceof Collection<?>) {
                        target = (Collection<Row>) targetObj;
                    } else {
                        throw new RuntimeException(String.format("不支持的类型【%s】", targetObj.getClass()));
                    }

                    String inferSelfColumn = habtm.inferSelfColumn();
                    String targetField = StrUtil.toCamelCase(habtm.inferTargetColumn());
                    String inferThroughSelfColumn = habtm.inferThroughSelfColumn();
                    String inferThroughTargetColumn = habtm.inferThroughTargetColumn();

                    List<Row> adding = new ArrayList<>();
                    for(Row t: target) {
                        Row ret = Row.ofKey(RowKey.FLEX_ID);
                        ret.set(inferThroughSelfColumn, mainRow.get(inferSelfColumn));
                        ret.set(inferThroughTargetColumn, t.get(targetField));
                        adding.add(ret);
                    }

                    String through = habtm.throughTableName();
                    if(!adding.isEmpty()) Db.insertBatch(through, adding);
                }
            }
        }
    }
}

